2023/04/05 更新: 為了避免本文章散落在不同網站,之後統一由部落格更新,再麻煩從部落格查看~
整個程式運作只會有此一個物件,不會創建第二個重複的物件
在實際情境中某些物件,比如連線 DB、與其他 service 長連線、取得環境 config,我們都希望可以整個運行環境「只有一個」,才不會造成資源的浪費,或者系統每個部分運行得到的值不同。
取得連線 DB 的物件,並且整個系統不會重複創建導致浪費 DB 連線的資源。
相關的 code 在Github - go-design-patterns
第一種方式(初始化即創建):在 import 模組的時候,將 db 透過init()
創建dbInstance{}
(init()
function 有著程式 runtime 會先行運行的特性),此後只有GetInstance()
這個公開方法可以取得dbInstance{}
,並無任何方法再創建dbInstance{}
優點:可以在初始化就創建 instance,不用等到呼叫才創建(第二種方式)
缺點:無法控制初始化的時間點,舉例來說有可能「環境變數」套件都還沒有初始化,db 就開始初始化了,導致讀不到正確的環境變數
// db/db.go
package db
type DBInstance struct{}
var dbInstance *DBInstance
func init() {
dbInstance = &DBInstance{}
}
func GetInstance() *DBInstance {
return dbInstance
}
// main file
package main
import "DAY-18/db"
func main() {
db.GetInstance()
}
為了解決缺點,所以許多專案會不以init()
來初始化物件,而是任意一個公開方法來初始化:
// db-2/db-2.go file
package db
type DBInstance struct{}
var dbInstance *DBInstance
func InitDB() {
dbInstance = &DBInstance{}
}
func GetInstance() *DBInstance {
return dbInstance
}
// main file
package main
import db "DAY-18/db-2"
func main() {
db.Init()
db.GetInstance()
}
第二種方式(lazy 方式):在呼叫GetInstance()
的時候會做檢查,如果dbInstance{}
不存在就創建,存在就回傳現存的dbInstance{}
優點:可以在真的用到此物件時,才創建此物件,以節省記憶體,這即是 lazy 方式的優點
缺點:由於在使用物件時才會創建,所以第一次呼叫時需要創建會花較多的時間
package main
type DBInstance struct{}
var dbInstance *DBInstance
func GetInstance() *DBInstance {
if dbInstance == nil {
dbInstance = &DBInstance{}
}
return dbInstance
}
func main() {
GetInstance()
}
如果覺得if dbInstance == nil
的檢查很麻煩的話,也可以用sync.Once{}
來實作,sync.Once{}.Do(func)
可以讓 function 裡的物件永遠只執行一次,以達到 Singleton 的效果,裡頭適用 CAS 演算法來實作:
package main
import "sync"
type DBInstance struct{}
var (
dbInstance *DBInstance
once = &sync.Once{}
)
func GetInstance() *DBInstance {
once.Do(func() {
dbInstance = &DBInstance{}
})
return dbInstance
}
func main() {
GetInstance()
}